package com.beyond.parse;

import com.beyond.parse.binding.CommonBoundSql;
import com.beyond.parse.binding.db.MySQLBoundSql;
import com.beyond.parse.binding.db.OracleBoundSql;
import com.beyond.parse.binding.db.PGBoundSql;
import com.beyond.parse.util.BeanUtil;
import com.beyond.parse.util.DruidUtil;
import com.beyond.parse.util.RegUtil;
import com.beyond.parse.util.XmlUtil;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.scripting.xmltags.*;
import org.apache.ibatis.session.Configuration;

import java.io.FileInputStream;
import java.io.InputStream;
import java.util.*;

public class XmlSQLParse {

    private String resource;

    private String dbType;

    private Map<String, List<String>> statementSQLMap = new HashMap<String, List<String>>();

    private Map<String, Map<String, String>> relatedMap = new HashMap<String, Map<String, String>>();

    private Map<String, Set<String>> parameterMap = new HashMap<String, Set<String>>();

    public XmlSQLParse(String resource, String dbType) {
        this.resource = resource;
        this.dbType = dbType;
    }

    public void parseXml() {
        String resource = XmlUtil.parseXml(this.resource);
        Configuration configuration = new Configuration();

        try {
            InputStream input = new FileInputStream(resource);

            XMLMapperBuilder builder = new XMLMapperBuilder(input, configuration, resource, configuration.getSqlFragments());
            builder.parse();

            Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
            configuration.getSqlFragments();

            Map<String, MappedStatement> stmtMap = new HashMap<String, MappedStatement>();
            for (MappedStatement statement : mappedStatements) {
                stmtMap.put(statement.getId(), statement);
            }

            Set<String> idKeys = stmtMap.keySet();
            for (String id : idKeys) {
                Map<String, String> fullParameterMap = new HashMap<String, String>();
                Map<String, String> simpleParameterMap = new HashMap<String, String>();
                List<String> ifNodeList = new ArrayList<String>();
                List<String> sqlList = new ArrayList<String>();

                MappedStatement statement = stmtMap.get(id);
                if (statement.getSqlSource() instanceof DynamicSqlSource) {
                    DynamicSqlSource sqlSource = (DynamicSqlSource) statement.getSqlSource();
                    SqlNode sqlNode = (SqlNode) BeanUtil.getProperty("rootSqlNode", sqlSource);

                    try {
                        parseParameterMap(sqlNode, fullParameterMap, ifNodeList);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

                // 最完整SQL
                String fullSql = statement.getBoundSql(fullParameterMap).getSql();
                sqlList.add(fullSql);

                Map<String, String> tmpMap = DruidUtil.parseParameterMap(fullSql, this.dbType);
                relatedMap.put(id, tmpMap);
                parameterMap.put(id, tmpMap.keySet());

                for (String key : fullParameterMap.keySet()) {
                    if(!ifNodeList.contains(key)) {
                        simpleParameterMap.put(key, fullParameterMap.get(key));
                    }
                }

                // 最简SQL
                String simpleSql = statement.getBoundSql(simpleParameterMap).getSql();
                sqlList.add(simpleSql);

                statementSQLMap.put(id, sqlList);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public List<String> getSQLList(String stmtId, Map<String, String> parameterTypeMap) throws Exception {
        List<String> sqlList = this.statementSQLMap.get(stmtId);

        if (sqlList.isEmpty()) {
            throw new Exception("stmtId is not exists");
        }

        Map<String, String> related = this.relatedMap.get(stmtId);
        Map<String, String> columnTypeMap = new HashMap<String, String>();
        for (Map.Entry entry : related.entrySet()) {
            columnTypeMap.put((String) entry.getValue() , parameterTypeMap.get(entry.getKey()));
        }

        CommonBoundSql boundSql = null;
        if ("oracle".equals(this.dbType)) {
            boundSql = new OracleBoundSql();
        } else if ("postgresql".equals(this.dbType)) {
            boundSql = new PGBoundSql();
        } else if ("mysql".equals(this.dbType)) {
            boundSql = new MySQLBoundSql();
        }

        List<String> result = boundSql.boundSql(columnTypeMap, sqlList);

        return result;
    }

    public Map<String, Set<String>> getParameterMap() {
        return parameterMap;
    }

    private static void parseParameterMap(SqlNode sqlNode, Map<String, String> parameterMap, List<String> ifNodeList) throws Exception{
        if (sqlNode instanceof MixedSqlNode) {
            List<SqlNode> nodeList = (List<SqlNode>) BeanUtil.getProperty("contents" , sqlNode);
            if (nodeList != null) {
                for (SqlNode node :nodeList) {
                    parseParameterMap(node, parameterMap, ifNodeList);
                }
            }
        } else if (sqlNode instanceof StaticTextSqlNode) {
            String text = (String) BeanUtil.getProperty("text", sqlNode);
            setParameterMap(text, parameterMap);
        } else if (sqlNode instanceof IfSqlNode) {
            String test = (String) BeanUtil.getProperty("test" , sqlNode);
            test = test.substring(0, test.indexOf("!=")).trim();
            ifNodeList.add(test);

            SqlNode node = (SqlNode) BeanUtil.getProperty("contents" , sqlNode);
            parseParameterMap(node, parameterMap, ifNodeList);
        } else if (sqlNode instanceof WhereSqlNode) {

        } else if (sqlNode instanceof TrimSqlNode) {
            SqlNode node = (SqlNode) BeanUtil.getProperty("contents" , sqlNode);
            parseParameterMap(node, parameterMap, ifNodeList);
        } else if (sqlNode instanceof TextSqlNode) {
            String text = (String) BeanUtil.getProperty("text", sqlNode);
            setParameterMap(text, parameterMap);
        }
    }


    private static void setParameterMap(String text, Map<String, String> parameterMap) {
        List<String> parameterNames = getParameterName(text);
        for (String param : parameterNames) {
            if (param != null) {
                parameterMap.put(param, "'&" + param + "&'");
            }
        }
    }


    private static List<String> getParameterName(String sqlText) {
        List<String> paramList;

        if (sqlText == null || sqlText.length() <= 0) {
            return null;
        }

        String reg = "[#$]\\{((?:(?!\\}_)[\\s\\S])*)\\}";
        paramList = RegUtil.regSubstring(sqlText, reg);

        return paramList;
    }
}
